package com.github.wxiaoqi.security.gate.v2.filter;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.server.ServerWebExchange;

import com.alibaba.fastjson.JSONObject;
import com.github.wxiaoqi.security.api.vo.authority.PermissionInfo;
import com.github.wxiaoqi.security.api.vo.log.LogInfo;
import com.github.wxiaoqi.security.auth.client.config.ServiceAuthConfig;
import com.github.wxiaoqi.security.auth.client.config.UserAuthConfig;
import com.github.wxiaoqi.security.auth.client.jwt.ServiceAuthUtil;
import com.github.wxiaoqi.security.auth.client.jwt.UserAuthUtil;
import com.github.wxiaoqi.security.auth.common.util.jwt.IJWTInfo;
import com.github.wxiaoqi.security.common.context.BaseContextHandler;
import com.github.wxiaoqi.security.common.msg.BaseResponse;
import com.github.wxiaoqi.security.common.msg.auth.TokenForbiddenResponse;
import com.github.wxiaoqi.security.gate.v2.feign.ILogService;
import com.github.wxiaoqi.security.gate.v2.feign.IUserService;
import com.github.wxiaoqi.security.gate.v2.utils.DBLog;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * @author ace
 * @create 2018/3/12.
 */
@Configuration
@Slf4j
public class AccessGatewayFilter implements GlobalFilter {
	@Autowired
	@Lazy
	private IUserService userService;
	@Autowired
	@Lazy
	private ILogService logService;

	@Value("${gate.ignore.startWith}")
	private String startWith;

	// @Value("${zuul.prefix}")
	// private String zuulPrefix;

	private static final String GATE_WAY_PREFIX = "/api";
	@Autowired
	private UserAuthUtil userAuthUtil;

	@Autowired
	private ServiceAuthConfig serviceAuthConfig;

	@Autowired
	private UserAuthConfig userAuthConfig;

	@Autowired
	private ServiceAuthUtil serviceAuthUtil;

	@Override
	public Mono<Void> filter(ServerWebExchange serverWebExchange, GatewayFilterChain gatewayFilterChain) {
		log.info("check token and user permission....");
		LinkedHashSet<URI> requiredAttribute = serverWebExchange
				.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
		ServerHttpRequest request = serverWebExchange.getRequest();
		String requestUri = request.getPath().pathWithinApplication().value();
		if (requiredAttribute != null) {
			Iterator<URI> iterator = requiredAttribute.iterator();
			while (iterator.hasNext()) {
				URI next = iterator.next();
				if (next.getPath().startsWith(GATE_WAY_PREFIX)) {
					requestUri = next.getPath().substring(GATE_WAY_PREFIX.length());
				}
			}
		}
		final String method = request.getMethod().toString();
		BaseContextHandler.setToken(null);
		ServerHttpRequest.Builder mutate = request.mutate();
		// 不进行拦截的地址
		if (isStartWith(requestUri)) {
			ServerHttpRequest build = mutate.build();
			return gatewayFilterChain.filter(serverWebExchange.mutate().request(build).build());
		}
		IJWTInfo user = null;
		try {
			user = getJWTUser(request, mutate);
		} catch (Exception e) {
			log.error("用户Token过期异常", e);
			return getVoidMono(serverWebExchange, new TokenForbiddenResponse("User Token Forbidden or Expired!"));
		}
		List<PermissionInfo> permissionIfs = userService.getAllPermissionInfo();
		// 判断资源是否启用权限约束
		Stream<PermissionInfo> stream = getPermissionIfs(requestUri, method, permissionIfs);
		List<PermissionInfo> result = stream.collect(Collectors.toList());
		PermissionInfo[] permissions = result.toArray(new PermissionInfo[] {});
		if (permissions.length > 0) {
			if (checkUserPermission(permissions, serverWebExchange, user)) {
				return getVoidMono(serverWebExchange,
						new TokenForbiddenResponse("User Forbidden!Does not has Permission!"));
			}
		}
		// 申请客户端密钥头
		mutate.header(serviceAuthConfig.getTokenHeader(), serviceAuthUtil.getClientToken());
		ServerHttpRequest build = mutate.build();
		return gatewayFilterChain.filter(serverWebExchange.mutate().request(build).build());

	}

	/**
	 * 网关抛异常
	 *
	 * @param body
	 */
	@NotNull
	private Mono<Void> getVoidMono(ServerWebExchange serverWebExchange, BaseResponse body) {
		serverWebExchange.getResponse().setStatusCode(HttpStatus.OK);
		byte[] bytes = JSONObject.toJSONString(body).getBytes(StandardCharsets.UTF_8);
		DataBuffer buffer = serverWebExchange.getResponse().bufferFactory().wrap(bytes);
		return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
	}

	/**
	 * 获取目标权限资源
	 *
	 * @param requestUri
	 * @param method
	 * @param serviceInfo
	 * @return
	 */
	private Stream<PermissionInfo> getPermissionIfs(final String requestUri, final String method,
			List<PermissionInfo> serviceInfo) {
		return serviceInfo.parallelStream().filter(new Predicate<PermissionInfo>() {
			@Override
			public boolean test(PermissionInfo permissionInfo) {
				String uri = permissionInfo.getUri();
				if (uri.indexOf("{") > 0) {
					uri = uri.replaceAll("\\{\\*\\}", "[a-zA-Z\\\\d]+");
				}
				String regEx = "^" + uri + "$";
				return (Pattern.compile(regEx).matcher(requestUri).find()) && method.equals(permissionInfo.getMethod());
			}
		});
	}

	private void setCurrentUserInfoAndLog(ServerWebExchange serverWebExchange, IJWTInfo user, PermissionInfo pm) {
		String host = serverWebExchange.getRequest().getRemoteAddress().toString();
		LogInfo logInfo = new LogInfo(pm.getMenu(), pm.getName(), pm.getUri(), new Date(), user.getId(), user.getName(),
				host);
		DBLog.getInstance().setLogService(logService).offerQueue(logInfo);
	}

	/**
	 * 返回session中的用户信息
	 *
	 * @param request
	 * @param ctx
	 * @return
	 */
	private IJWTInfo getJWTUser(ServerHttpRequest request, ServerHttpRequest.Builder ctx) throws Exception {
		List<String> strings = request.getHeaders().get(userAuthConfig.getTokenHeader());
		String authToken = null;
		if (strings != null) {
			authToken = strings.get(0);
		}
		if (StringUtils.isBlank(authToken)) {
			strings = request.getQueryParams().get("token");
			if (strings != null) {
				authToken = strings.get(0);
			}
		}
		ctx.header(userAuthConfig.getTokenHeader(), authToken);
		BaseContextHandler.setToken(authToken);
		return userAuthUtil.getInfoFromToken(authToken);
	}

	private boolean checkUserPermission(PermissionInfo[] permissions, ServerWebExchange ctx, IJWTInfo user) {
		List<PermissionInfo> permissionInfos = userService.getPermissionByUsername(user.getUniqueName());
		PermissionInfo current = null;
		for (PermissionInfo info : permissions) {
			boolean anyMatch = permissionInfos.parallelStream().anyMatch(new Predicate<PermissionInfo>() {
				@Override
				public boolean test(PermissionInfo permissionInfo) {
					return permissionInfo.getCode().equals(info.getCode());
				}
			});
			if (anyMatch) {
				current = info;
				break;
			}
		}
		if (current == null) {
			return true;
		} else {
			if (!RequestMethod.GET.toString().equals(current.getMethod())) {
				setCurrentUserInfoAndLog(ctx, user, current);
			}
			return false;
		}
	}

	/**
	 * URI是否以什么打头
	 *
	 * @param requestUri
	 * @return
	 */
	private boolean isStartWith(String requestUri) {
		boolean flag = false;
		for (String s : startWith.split(",")) {
			if (requestUri.startsWith(s)) {
				return true;
			}
		}
		return flag;
	}

	/**
	 * 网关抛异常
	 *
	 * @param body
	 * @param code
	 */
	@SuppressWarnings("unused")
	private Mono<Void> setFailedRequest(ServerWebExchange serverWebExchange, String body, int code) {
		serverWebExchange.getResponse().setStatusCode(HttpStatus.OK);
		return serverWebExchange.getResponse().setComplete();
	}

}
